package sise.world;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JOptionPane;

import org.apache.log4j.Logger;

import sise.world.agent.AbstractAgent;
import sise.world.agent.AbstractAgent.AgentType;
import sise.world.agent.InfoToAgent;
import sise.world.map.Field;
import sise.world.map.Field.FieldType;
import sise.world.map.WorldMap;
import sise.world.utils.SimulationConfiguration;

public class World {

	private static final Logger logger = Logger.getLogger(World.class);

	private WorldMap worldMap;
	private Random r;
	private List<AbstractAgent> agents;
	private List<AbstractAgent> deadAgents;
	private SimulationConfiguration cfg;

	public World() {
		this.agents = new ArrayList<AbstractAgent>();
		this.deadAgents = new ArrayList<AbstractAgent>();
		r = new Random();
	}

	// korzysta z tego GUI
	public void nextTurn() {
		logger.info("Running next turn");
		int rows = cfg.getRows() - 1;// worldMap.getFields().length - 1;
		int cols = cfg.getCols() - 1;// worldMap.getFields()[0].length - 1;
		int next;
		int wormCounter = 0;
		int treeCounter = 0;
		int foodCounter = 0;
		int wormProp = 0;
		int treeProp = 0;
		int foodProp = 0;
		int changes = 0;
		Field currentField;
		for (int row = 1; row < rows; row++) {
			for (int col = 1; col < cols; col++) {
				wormCounter = treeCounter = foodCounter = 0;
				next = r.nextInt(1000); // 100% = 1000 !

				for (int i = -1; i != 2; i++) {
					for (int j = -1; j != 2; j++) {
						// if (i != 0 && j != 0) {
						switch (worldMap.getFields()[row + i][col + j].getFieldType()) {
						case FOOD: // pole z roslinka
							foodCounter++;
							break;
						case WORM: // pole z robakiem
							wormCounter++;
							break;
						case TREE: // pole z drzewem
							treeCounter++;
							break;
						default:
							break;
						}
						// }
					}
				}
				wormProp = (int) ((Math.pow(cfg.getNextTurnWormBase(), wormCounter) - cfg.getNextTurnWormShift()) * 10);
				treeProp = (int) ((Math.pow(cfg.getNextTurnTreeBase(), treeCounter) - cfg.getNextTurnTreeShift()) * 10);
				foodProp = (int) ((Math.pow(cfg.getNextTurnFoodBase(), foodCounter) - cfg.getNextTurnFoodShift()) * 10);

				// zakomentujcie jak chcecie testowac, bo tylko bedzie opozniac
				// :)
				currentField = worldMap.getFields()[row][col];
				if (next < wormProp) {
					if (!currentField.getFieldType().equals(FieldType.WORM)) {
						changes++;
					}
					currentField.setFieldType(FieldType.WORM);
				} else if (next < wormProp + treeProp) {
					if (!currentField.getFieldType().equals(FieldType.TREE)) {
						changes++;
					}
					currentField.setFieldType(FieldType.TREE);
				} else if (next < wormProp + treeProp + foodProp) {
					if (!currentField.getFieldType().equals(FieldType.FOOD)) {
						changes++;
					}
					currentField.setFieldType(FieldType.FOOD);
				} else if (next < wormProp + treeProp + foodProp + cfg.getGroundRatio()) {
					if (!currentField.getFieldType().equals(FieldType.GROUND)) {
						changes++;
					}
					currentField.setFieldType(FieldType.GROUND);
				}
				// ZMIENIC QUANT
				currentField.modifyResources(r.nextInt(9) - 4);
			}
		}
		logger.info("Percent of changes: " + (double) (changes * 100) / ((rows + 1) * (cols + 1)));

		/*
		 * AGENCI - pobieramy info od wszystkich ogentow o tym jakie dzialanie
		 * chca zrobic i jesli to tylko mozliwe to swiat zmienia sie w skutek
		 * tych wlasnie dzialan
		 * 
		 */

		for (AbstractAgent agent : agents) {
			if (agent.getFoodQuantity() > 0 && agent.getHp() > 0) { // jezeli
																	// agent
																	// zyje
				
				agent.processNextMove(passInfoToAgent(agent)); // wywolywanie
																// procesu
																// myslowego
																// agenta
				switch (agent.getNextMove()) { // agent zwraca wynik procesu
												// myslowego
				case NOTHING:
					// System.out.println("nothing");
					break;
				case MOVE:
					walk(agent, agent.getNextActionParameters());
					// System.out.println("walk " +
					// agent.getNextActionParameters().getX() + " "
					// + agent.getNextActionParameters().getY());
					break;
				case COLLECT_FOOD:
					collectFood(agent);
					// System.out.println("collectFood");
					break;
				case KILL:
					kill(agent, agent.getNextActionParameters());
					//System.out.println("kill");
					break;
				case COLLECT_WORM:
					//collectFood(agent);
					collectWorm(agent);
					//collectWorm(agent);
					break;
				case CUT_TREE:
					cutTree(agent, agent.getNextActionParameters());
					// System.out.println("cut tree");
				default:
					break;
				}
				
				agent.setFoodQuantity(agent.getFoodQuantity() - 1);
				if (agent.getFoodQuantity() > 0) {
					agent.survived();
				} else {
					//
					worldMap.getFields()[(int) agent.getOwnField().getCoordinates().getX()][(int) agent.getOwnField()
							.getCoordinates().getY()].setPresentAgent(null);
					agent.setOwnField(null);
				}
			}
		}
	}

	public void walk(AbstractAgent agent, Point nextActionParameters) {
		int x = (int) agent.getOwnField().getCoordinates().getX();
		int y = (int) agent.getOwnField().getCoordinates().getY();

		int deltaX = (int) nextActionParameters.getY();
		int deltaY = (int) nextActionParameters.getX();

		if ((x + deltaX) < worldMap.getFields().length && (x + deltaX) >= 0
				&& (y + deltaY) < worldMap.getFields()[0].length && (y + deltaY) >= 0) {

			if (agent.getAgentType() != AgentType.DRAPIEZNIK) {
				if (worldMap.getFields()[x + deltaX][y + deltaY].getFieldType() != FieldType.MOUNTAIN
						//&& worldMap.getFields()[x + deltaX][y + deltaY].getFieldType() != FieldType.TREE
						&& worldMap.getFields()[x + deltaX][y + deltaY].getPresentAgent() == null) {
					worldMap.getFields()[x][y].setPresentAgent(null);
					worldMap.getFields()[x + deltaX][y + deltaY].setPresentAgent(agent);
					agent.setOwnField(worldMap.getFields()[x + deltaX][y + deltaY]);
				}
			} else {
				if (worldMap.getFields()[x + deltaX][y + deltaY].getFieldType() != FieldType.MOUNTAIN
						&& worldMap.getFields()[x + deltaX][y + deltaY].getPresentAgent() == null) {
					worldMap.getFields()[x][y].setPresentAgent(null);
					worldMap.getFields()[x + deltaX][y + deltaY].setPresentAgent(agent);
					agent.setOwnField(worldMap.getFields()[x + deltaX][y + deltaY]);
				}
			}

		} else {
			JOptionPane.showMessageDialog(null, "agent wyszedl poza krawedz swiata i spadl w wieczna otchlan");
			agent.setFoodQuantity(0);
			worldMap.getFields()[x][y].setPresentAgent(null);
		}
	}

	public void collectWorm(AbstractAgent agent) {
		Point coordinates = getAgentCoordinates(agent);
		int quantity = 1;

		if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getFieldType() == FieldType.WORM) {
			if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources() < 3) {
				quantity = worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources();
			}
			worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()]
					.setResources(worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()]
							.getResources() - quantity);
			agent.setScore(agent.getScore() + quantity);
			
			agent.setFoodQuantity(agent.getFoodQuantity() + quantity);
		
			
		}
		if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources() <= 0) {
			worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].setFieldType(FieldType.GROUND);
		}
	}
		
	public void collectFood(AbstractAgent agent) {
		Point coordinates = getAgentCoordinates(agent);
		int quantity;

		if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getFieldType() == FieldType.FOOD) {

			if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources() >= 5) {
				quantity = 5;
			} else {
				quantity = worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources();
			}

			worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()]
					.setResources(worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()]
							.getResources() - quantity);

			agent.setFoodQuantity(agent.getFoodQuantity() + quantity);
		}

		if (worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].getResources() <= 0) {
			worldMap.getFields()[(int) coordinates.getX()][(int) coordinates.getY()].setFieldType(FieldType.GROUND);
		}
	}

	/*
	 * Strzela w pole o koordynatach [x, y]. Agent za zabicie wroga dostaje 1
	 * punkt. Agent za zabicie swojego traci 1 punkt.
	 * 
	 * @param agent
	 *            - agent, ktory strzela
	 * @param y
	 *            - wspolrzedna y (bezwzgledna)
	 * @param x
	 *            - wspolrzedna x (bezwzgledna)
	 */
	
	public void kill(AbstractAgent agent, Point nextActionParameters) {
		int x = (int) agent.getOwnField().getCoordinates().getX() + (int) nextActionParameters.getY();
		int y = (int) agent.getOwnField().getCoordinates().getY() + (int) nextActionParameters.getX();
		// calculate damage
		int damage = 0;
		if (agent.getAgentType().name().equals(AgentType.DRAPIEZNIK.name())) {
			damage = 1;

			
			/*if (agent.getBoostTime() > 0) {
				damage = 2;
			}*/

		} else {
			System.out.println("bystreta killuje");
			int propability = 50;
			if (new Random().nextInt(100) < propability) {
				damage = 1;
			}
		}
		// deal damage
		if (worldMap.getFields()[x][y].getPresentAgent() != null) {
			AbstractAgent presentAgent = worldMap.getFields()[x][y].getPresentAgent();
			presentAgent.dealDamage(damage);
			if (presentAgent.getHp() <= 0) {
				// remove agent from the map
				presentAgent.setOwnField(null);
				worldMap.getFields()[x][y].setPresentAgent(null);
				// calculate score
				int score = 1;
				if (presentAgent.getAgentType().name().equals(agent.getAgentType().name())) {
					score = -1;
				} else if (agent.getAgentType().name().equals(AbstractAgent.AgentType.BYSTRETKA_MIESOZERNA.name())
						&& presentAgent.getAgentType().name().equals(AbstractAgent.AgentType.BYSTRETKA_ROSLINOZERNA.name())) {
					score = -1;
				}
				agent.addScore(score);
			}
		}
	}

	public void cutTree(AbstractAgent agent, Point nextActionParameters) {
		int quantity = 1;
		
		int x = (int) agent.getOwnField().getCoordinates().getX() + (int) nextActionParameters.getY();
		int y = (int) agent.getOwnField().getCoordinates().getY() + (int) nextActionParameters.getX();

		agent.setScore(agent.getScore() + quantity);
		if (worldMap.getFields()[x][y].getFieldType() == FieldType.TREE && agent.getAgentType() == AgentType.BYSTRETKA_ROSLINOZERNA) {
			worldMap.getFields()[x][y].setFieldType(FieldType.GROUND);
		}
	}

	public Point getAgentCoordinates(AbstractAgent agent) {
		return agent.getOwnField().getCoordinates();
	}

	public WorldMap getWorldMap() {
		return worldMap;
	}

	public InfoToAgent passInfoToAgent(AbstractAgent agent) { // daje info do agenta o polu wokol niego range = 2
		InfoToAgent info = new InfoToAgent();
		Point agentPosition = getAgentCoordinates(agent);
		// TMP - kazdy agent widzi tylko na 2 pola w kazda stronę
		int range = 2;
		for (int diffX = -range; diffX <= range; diffX++) {
			for (int diffY = -range; diffY <= range; diffY++) {
				if (positionInMap(agentPosition, diffX, diffY, worldMap)) {
					Field field = worldMap.getFields()[agentPosition.x + diffX][agentPosition.y + diffY];
					AbstractAgent agentOnField = field.getPresentAgent();
					if (agentOnField != null && (diffY != 0 || diffX != 0)) {
						info.addObserved(agentOnField.getOwnField().getCoordinates(), agentOnField.getAgentType()); 
						// jezeli  jest jakis agent w polu widzenia  i nie jest to ten sam agent to dostajemy dodatkowe info o tym agencie
					}
					info.addField(field.getCoordinates(), field); 
					// dla kazdego pola w polu widzenia dostajemy info o tym polu(przez info to agent)
				}
			}
		}
		return info;
	}

	private boolean positionInMap(Point position, int dx, int dy, WorldMap map) { // czy agent jest na mapie
		return position.x + dx >= 0 && position.y + dy >= 0 && position.x + dx < map.getXSize()
				&& position.y + dy < map.getYSize();
	}

	public void setWorldMap(WorldMap worldMap, SimulationConfiguration cfg) {
		this.cfg = cfg;
		this.worldMap = worldMap;
		for (int i = 0; i < this.worldMap.getFields().length; i++) {
			for (int j = 0; j < this.worldMap.getFields()[0].length; j++) {
				if (this.worldMap.getFields()[i][j].getPresentAgent() != null) {
					this.agents.add(this.worldMap.getFields()[i][j].getPresentAgent());
				}
			}
		}
	}

	public boolean hasAliveAgent() {
		for (AbstractAgent agent : agents) {
			if (agent.isAlive()) {
				return true;
			}
		}
		return false;
	}

	public List<AbstractAgent> getAgents() {
		return agents;
	}

	public void setAgents(List<AbstractAgent> agents) {
		this.agents = agents;
	}

	public List<AbstractAgent> getDeadAgents() {
		return deadAgents;
	}

	public void setDeadAgents(List<AbstractAgent> deadAgents) {
		this.deadAgents = deadAgents;
	}

}
